
var click_div;
var clicked_down_callback = null;

var fixed = null;
var v = new Object();

var pell = 1 + Math.sqrt(2);
var sqrt2 = Math.sqrt(2);

var auto_update = false;
var options_visible = false;
var auto_timeout = null;

var opts = [];
var bars = [];
var ints = [];
var drawn = [];

var cartwheels = [];
var a5_verts = [];
var a5_tiles = [];
var a5_edges = [];


var expand;
var seed;
var seed_d;
var seed_a;

var fresh_cartwheels = [];
var fresh_tiles = [];
var cartwheel_r = null;

var draw_tiles = [];
/*

   the sqrt2 between long and short is 

   (2 + sqrt(2)) / (1 + sqrt(2))

   which is equal to sqrt(2)

*/

function touch_moved(e) { 
	e.preventDefault();
}

function touch_ended(e) { 
	e.preventDefault();
}

function touch_started(e) {
	//debug_log('touch_started');
	e.preventDefault();
	if (e.touches.length < 1) {
		return;
	}
	var o = {
		clientX : e.touches[0].pageX,
		clientY : e.touches[0].pageY,
		preventDefault: (function () { })
	};
	clicked_down(o);
}

function clicked_down(e) {
	e.preventDefault();
	if (null == clicked_down_callback) { return; }
	clicked_down_callback(e);
}

function init() {
	//document.addEventListener("touchstart", touch_started, false);
	document.addEventListener("touchmove", touch_moved, false);
	//document.addEventListener("touchend", touch_ended, false);
	click_div = document.getElementById('click_div');
	click_div.style.backgroundColor = "rgba(0,0,0,0)";
	canvas_init();
	query_init();
	check_fixed();
	init_options();
	clicked_down_callback = view_click;
	click_div.addEventListener('mousedown', clicked_down, false);
	click_div.addEventListener('touchstart', touch_started, false);
	window.onresize = on_resize;
	/*
	for (var i = 0; i < 60; ++i) {
		debug_log("musical_distance(" + i + ") = " + get_musical_distance(i));
	}
	*/
	on_resize();
}

function on_resize() {
	if (null == fixed) {
		canvas.width = window.innerWidth;
		canvas.height = window.innerHeight;
	} else {
		canvas.width = fixed.w;
		canvas.height = fixed.h;
		document.body.style.backgroundColor = 'gray';
	}
	click_div.style.width = window.innerWidth + 'px';
	click_div.style.height = window.innerHeight + 'px';
	ctx.globalCompositeOperation = 'lighter';
	set_viewport();
}

function view_scroll(x, y) {
	//how big is the screen?
	var hx =  v.d.w; //canvas.width / v.z;
	var hy =  v.d.h; //canvas.height / v.z;
	set_viewport(v.x + x * hx, v.y + y * hy, v.z);
}

function view_click(e) {
	auto_update = false;
	var hx = 1 - 2 * e.clientX / canvas.width;
	var hy = 1 - 2 * e.clientY / canvas.height;
	view_scroll(-hx, -hy);
}

function set_viewport(x, y, z) {
	v.x = ((null == x) ? v.x : x);
	v.y = ((null == y) ? v.y : y);
	v.z = ((null == z) ? v.z : z);
	if (null == v.x) { v.x = 0; }
	if (null == v.y) { v.y = 0; }
	if (null == v.z) { v.z = 8; }

	// what are the edges of the viewport ?
	// z is pixels/unit
	var w = .5 * canvas.width / v.z;
	var h = .5 * canvas.height / v.z;

	v.l = [{x: v.x - w, y: v.y - h}, {x: v.x - w, y: v.y + h}];
	v.t = [{x: v.x - w, y: v.y - h}, {x: v.x + w, y: v.y - h}];
	v.r = [{x: v.x + w, y: v.y - h}, {x: v.x + w, y: v.y + h}];
	v.b = [{x: v.x - w, y: v.y + h}, {x: v.x + w, y: v.y + h}];

	v.d = new Object;
	v.d.w = w;
	v.d.h = h;
	v.d.l = v.x - v.d.w;
	v.d.t = v.y - v.d.h;
	v.d.r = v.x + v.d.w;
	v.d.b = v.y + v.d.h;

	v.cx = canvas.width / 2;
	v.cy = canvas.height / 2;

	redraw();
}

function check_fixed() {
	if (
		(null != query['w']) && 
		(null != query['h'])
	) { 
		fixed = {
			w: query['w'],
			h: query['h']
		};
	}
}

function view_zoom(z) {
	set_viewport(v.x, v.y, v.z * Math.pow((sqrt2), z));
}

function show_options() {
	if (null != auto_timeout) {
		clearTimeout(auto_timeout);
		auto_timeout = null;
	}

	var e;
	e = document.getElementById('form_div_bottom');
	if (null != e) {
		e.style.top = (16 + window.innerHeight - e.clientHeight) + 'px';
		e.style.left= ((window.innerWidth - e.clientWidth) / 2) + 'px';
	}

	e = document.getElementById('form_div_top');
	if (null != e) {
		e.style.top = '-18px';
		e.style.left= ((window.innerWidth - e.clientWidth) / 2) + 'px';
	}

	for (var i in opts) {
		e = document.getElementById('opt_' + i);
		if (null != e) {
			e.style.backgroundColor = ( 
				opts[i] ?  'black' : 'white' 
			);
			e.style.color = ( 
				opts[i] ?  'white' : 'black' 
			);
		}
	}

	e = document.getElementById('opt_auto');
	if (null != e) {
		e.style.backgroundColor = ( 
			auto_update ? 'black' : 'white' 
		);
		e.style.color = ( 
			auto_update ?  'white' : 'black' 
		);
	}

	if (options_visible) { 
		document.getElementById('form_div_top').style.visibility = 'visible';
		document.getElementById('form_div_bottom').style.visibility = 'visible';
		clicked_down_callback = view_click;
	} else {
		hide_options();
	}
}

function stop_auto(e) {
	auto_update = false;
	options_visible = true;
	show_options();
}

function hide_options() {
	options_visible = false;
	clicked_down_callback = stop_auto;
	document.getElementById('form_div_top').style.visibility = 'hidden';
	document.getElementById('form_div_bottom').style.visibility = 'hidden';
}

function hide_help(ev) {
	if (null != ev) {ev.preventDefault();}
	var e = document.getElementById('help');
	e.style.visibility = 'hidden';
	options_visible = true;
	show_options();
}

function show_help() {
	var e = document.getElementById('help');
	hide_options();
	e.style.visibility = 'visible';
	e.style.top = ((window.innerHeight - e.clientHeight) / 2) + 'px';
	e.style.left= ((window.innerWidth - e.clientWidth) / 2) + 'px';
	clicked_down_callback = hide_help;
}

function clear_globals() {
	drawn = [];
	cartwheels = [];
	fresh_cartwheels = [];
	fresh_tiles = [];
	draw_tiles = [];
	seed = null;
	bars = [];
	ints = [];
	a5_verts = [];
	a5_tiles = [];
	a5_edges = [];
}

function redraw() {
	window.scrollTo(0,0);
	is_drawing = true;
	clear_globals();
	canvas_clear();
	/*
	for (var g = 0; g < 4; ++g) {
		for (var n = 0; n < 128; ++n) {
			find_bar(g, n);
			find_bar(g, -n);
		}
	}
	*/
	//draw_bars(bars, .25);
	//find_cartwheel([0, 0, 0, 0]);
	find_seed_cartwheel(10);
	//find_tangent_cartwheels([0, 0, 0, 0]);
	/*
	for (var i = 0; i < 5; ++i) {
		var fresh = [];
		for (var k in cartwheels) {
			if (null == cartwheels[k].tangents) {
				fresh[k] = cartwheels[k];
			}
		}
		for (var k in fresh) {
			fresh[k].tangents = find_tangent_cartwheels(fresh[k].a);
		}
	}
	*/
	//draw_bars(bars, .25);

	show_options();
}

function find_tangent_cartwheels(a) {
	//debug_log('find_tangents: ' + a.join(':'))
	var t = [
		[5, 0, -5, 7],
		[0, 5, -7, 5],
		[-5, 7, -5, 0],
		[-7, 5, 0, -5],
		[-5, 0, 5, -7],
		[0, -5, 7, -5],
		[5, -7, 5, 0],
		[7, -5, 0, 5]
	];
	var r = [];

	var c = [];
	for (var i in t) {
		for (var j in a) {
			c[j] = a[j] + t[i][j];
		}
		var o = find_cartwheel(c);
		if (null != o) {
			r[o.k] = cartwheels[o.k];
		}
	}

	//draw_bars(bars, .25, 1);
	return r;
}

function debug_log(s) {
	var e;
	try {
		console.log(s);
	} catch (e) { }
}

function check_offline() {
	if (!navigator.onLine) {
		var e = document.getElementById('share_link');
		if (null == e) { return; }
		e.style.visibility = 'hidden';
	}
}

function init_options() {
	check_offline();
	opts = {
		a4: false,
		a4d: false,
		bars: false,
		edges: false,
		fill: false,
		octagon: false,
		gray: false
	};

	for (var i in opts) {
		if (null != query[i]) {
			opts[i] = true;
			q = true;
		}
	}

	var q = false;

	var a = ['x', 'y', 'z'];
	for (var i in a) {
		if (null != query[a[i]]) {
			v[a[i]] = query[a[i]] - 0;
			q = true;
		}
	}

	if (q) {
		/*
		seed_a = [];
		for (var i = 0; i < 5; ++i) {
			seed_a[i] = query['s' + i] - 0;
			if (isNaN(seed_a[i])) {
				seed_a[i] = 0;
			}
		}
		*/
		auto_update = false;
	} else {
		auto_update = true;
		random_options();
	}
}

function changed_options(o) {

	var chosen = o;

	if (null == chosen) {
		chosen = document.getElementById('options').value;
	}

	for (var i in opts) { if (chosen == i) {
		opts[i] = ! opts[i];
		return redraw();
	} }
	if ('zoom_in' == chosen) {
		return view_zoom(1);
	} else if ('zoom_out' == chosen) {
		return view_zoom(-1);
	} else if ('save' == chosen) {
		return canvas_save();
	} else if ('share' == chosen) {
		if (null == fixed) {
			return share_tiling();
		} else {
			return canvas_save();
		}
	} else if ('change' == chosen) {
		return random_redraw();
	} else if ('hide' == chosen) {
		return hide_options();
	} else if ('auto' == chosen) {
		auto_update = !auto_update;
		return redraw();
	}
}

function nothing_shows() { 
	return !(
		opts['a4'] || 
		opts['a4d'] || 
		opts['edges'] || 
		opts['bars'] || 
		opts['fill'] || 
	opts['octagon']);
}

function random_options() {
	do {
		for (var i in opts) {
			opts[i] = (.5 > Math.random());
		}
	} while (nothing_shows());
	if (.5 > Math.random()) { opts['gray'] = false; }
	/*
	opts['bars'] = false;
	opts['gray'] = true;
	opts['edges'] = true;
	opts['octagon'] = true;
	opts['a4d'] = true;
	opts['a4'] = false;
	*/
}

function random_redraw() {

	random_options();

	if (.5 > Math.random()) { return redraw(); }
	if (.5 > Math.random()) { return view_scroll(
		(2 - 4 * Math.random()),
		(2 - 4 * Math.random())
	); }
	if (.5 > Math.random()) {
		return view_zoom(1 - Math.random() * 2);
	}
	return redraw();
}

function share_opts() {
	var s = [];
	for (var i in opts) {
		if (opts[i]) { s.push(i + '=1'); }
	}
	s.push('x=' + v.x);
	s.push('y=' + v.y);
	s.push('z=' + v.z);
	/*
	for (var i in seed_a) {
		s.push('s' + i + '=' + seed_a[i]);
	}
	*/
	s.push('patch=ammann-beenker');
	return s.join('&');
}

function share_tiling() {
	var u = '../share/share.html?' + share_opts();
	document.location = u;
}

var middle_d;

function expand_middle_d() {
	// the "middle d" sequence is like the middle c sequence,
	// but it is based on pell numbers rather than fibonacci numbers
	//
	// it's probably called something else
	//
	// expand the middle d sequence by appending the last two
	// sequences onto it
	//
	// the first sequence is [S]
	// the second is [S, L]
	//
	// the third is second + first + second 
	// the fourth is third + second + third
	//
	// we can turn the third sequence into the fourth by appending
	// the second onto it, and then the third
	//
	// so all we have to keep track of between sequences is the length
	// of the previous sequence
	// 
	/*
	var np = middle_d.m.length;
	var a = middle_d.m.slice(0, np);
	var b = middle_d.m.slice(0, middle_d.p);
	var c = a.slice(0);
	middle_d.m = a.concat(b, c);
	middle_d.p = np;
	*/
	// brute force always works
	// except when it doesn't
	var np = middle_d.m.length;
	var o = middle_d.m;
	var a = middle_d.a;
	var b = middle_d.b;
	var na = []; na = na.concat(a, b);
	var nb = []; nb = nb.concat(a, a, b);
	o = o.concat(na, nb);
	middle_d.a = na;
	middle_d.b = nb;
	middle_d.m = o;
	for (var i = np; i < middle_d.m.length; ++i) {
		middle_d.S[i] = middle_d.S[i - 1];
		middle_d.L[i] = middle_d.L[i - 1];
		++middle_d[middle_d.m[i]][i];
	}
	/*
	debug_log("middle_d.m = [" + middle_d.m.join(' ') + ']');
	debug_log("middle_d.S = [" + middle_d.S.join(' ') + ']');
	debug_log("middle_d.L = [" + middle_d.L.join(' ') + ']');
	debug_log("middle_d.p = " + middle_d.p);
	*/
}

function get_musical_gaps(t) {
	if (0 == t) { return ['S', 'L']; }
	var m = ((t < 0) ? -1 : 1);
	if (1 == t) { return ['L', 'S']; }
	if (-1 == t) { return ['S', 'S']; }
	var i = Math.abs(t) - 2;
	return ([middle_d_term(i), middle_d_term(i + 1)]);
}

function get_musical_distance(t) {
	var c = (1 + sqrt2) / 2;
	if (0 == t) { return - c + 1; }
	// if this is term 0, return the center;
	var m = ((t < 0) ? -1 : 1);
	var i = Math.abs(t) - 2;
	middle_d_term(i + 5);
	var d = c * m;
	// if this is term 1 or -1, return the edge
	if (i < 0) { return d; }
	/*
	if (i > 2) {
		d += m * (middle_d.S[1] + middle_d.L[1] * sqrt2);
		i -= 2;
	}
	*/
	d += m * (middle_d.S[i] + middle_d.L[i] * sqrt2);
	//debug_log("musical " + t + " = " + i + ' = ' + middle_d.m[i] + ' = ' + d);
	return d;
}

function middle_d_term(n) {
	var t = Math.abs(n);
	//if (0 == t) { return 'S'; }
	//if (1 == t) { return 'L'; }
	//if (t > 1) { t -= 2; }
	//else { ++t; t *= -1; }
	if (null == middle_d) { middle_d = {
		m: ['S', 'L'],
		a: ['S'],
		b: ['L'],
		S: [1, 1], // accumulate a count of shorts
		L: [0, 1], // accumulate a count of longs
		p: 1
	}}
	while (middle_d.m.length < t) {
		expand_middle_d();
	}
	return middle_d.m[t];
}

function find_bar(g, n) {
	var k = g + ":" + n;
	var o = bars[k];
	if (null == o) {
		var d = (g % 2) ? 1 : -1;
		var c = find_center_bar(g);
		var md = d * get_musical_distance(n);
		var bar = find_parallel_line(c.bar, md);
		var gaps = get_musical_gaps(n);
		var bold = ((gaps[0] == 'S') && (gaps[1] == 'S'));
		//debug_log(n + ' ' + get_musical_gaps(n).join('-'));
		o = {
			k: k,
			g: g,
			i: n,
			typ: 'bar',
			bar: bar,
			theta: c.theta,
			bold: bold,
			d: md
		};
		o.color = rainbow_numbers(.5 - d * o.d / 96);
		if (opts['gray']) { o.color = [127, 127, 127]; }
		bars[k] = o;
	}
	return o;
}

function get_center_bar(theta) {
	return get_bar(0, 0, theta);
}

function get_bar(x, y, theta) {
	return [{x: x, y:y}, {x: x + Math.cos(theta), y: y - Math.sin(theta)}]
}

function find_center_bar(g) {
	var theta = (1 + 2 * g) * Math.PI / 8;
	return {k: g, theta: theta, bar: get_center_bar(theta)};
}

function find_parallel_line(p, r) {

	var dx = (p[0].x - p[1].x);
	var dy = (p[0].y - p[1].y);

	var th = Math.atan2(dy, dx) - Math.PI / 2;

	var o = new Array();
	for (var i  in p) {
		o[i] = {x: p[i].x, y: p[i].y};
		o[i].x += r * Math.cos(th);
		o[i].y += r * Math.sin(th);
	}

	return o;
}

function find_cartwheel(a) {
	var k = '(' + a.join(':') + ')';
	if (null != cartwheels[k]) { return cartwheels[k]; }
	var b = [];
	for (var g = 0; g < 4; ++g) {
		b[g] = [find_bar(g, a[g])];
		for (var i = 1; i < 5; ++i) {
			b[g].push(find_bar(g, a[g] + i));
			b[g].unshift(find_bar(g, a[g] - i));
		}
	}
	// this is a valid cartwheel if only bar 3 or 5 in each group is bold

	var bb = [];
	
	for (var g in b) {
		var bolded = false;
		for (var i = 1; i < 8; ++i) {
			if (b[g][i].bold) {
				if (bolded) { return null; }
				if ((3 == i) || (5 == i)) { 
					bb[g] = b[g][i];
					bolded = true; 
					continue;
				}
				return null;
			}
		}
		if (! bolded) { return null; }
	}


	cartwheels[k] = {
		k: k,
		typ: 'cartwheel',
		a: a.slice(0),
		lines: b,
		color: find_color(bb),
		bars: bb
	};
	// draw cartwheel bars
	//draw_bars(bb, 1);
	for (var g in b) {
		//draw_bars(b[g], .1, 1);
		for (var i in b[g]) {
			//fresh_tiles.push(b[g][i]);
		}
	}
	cartwheels[k].verts = find_cartwheel_verts(cartwheels[k]);

	var pt = get_polygon_center(cartwheels[k].verts);
	cartwheels[k].x = pt.x;
	cartwheels[k].y = pt.y;

	if (null == cartwheel_r) {
		cartwheel_r = 2 * get_distance(cartwheels[k], cartwheels[k].verts[0]);
		//debug_log('cartwheel_r = ' + cartwheel_r);
	}

	orient_cartwheel(cartwheels[k]);

	find_a5_tiles(cartwheels[k]);
	find_cartwheel_a4_edges(cartwheels[k]);

	/*
	ctx.strokeStyle = parse_color(cartwheels[k].color, .25);
	ctx.lineWidth = 2;

	for (var i in cartwheels[k].verts) {
		var vert = cartwheels[k].verts[i];
		var iv = cartwheels[k].inner_verts[i];
		if (null == iv) { continue; }
		draw_segments(scale_points([vert, iv]));
	}
	ctx.strokeStyle = parse_color(cartwheels[k].color, .5);
	ctx.lineWidth = 1;
	draw_segments(scale_points([cartwheels[k], 
		cartwheels[k].verts[
			cartwheels[k].orientation]]));

	*/
	var p = cartwheels[k].verts;
	/*
	p.push(find_intersection(b[0][8], b[3][8]));
	p.push(find_intersection(b[2][0], b[3][8]));
	p.push(find_intersection(b[2][0], b[1][8]));
	p.push(find_intersection(b[0][0], b[1][8]));
	p.push(find_intersection(b[0][0], b[3][0]));
	p.push(find_intersection(b[2][8], b[3][0]));
	p.push(find_intersection(b[2][8], b[1][0]));
	p.push(find_intersection(b[0][8], b[1][0]));
	*/

	//ctx.strokeStyle = parse_color(cartwheels[k].color, .5);
	//draw_polygon(scale_points(p));
	//ctx.fillStyle = parse_color(cartwheels[k].color, .1);
	//fill cartwheel octagon
	//fill_polygon(scale_points(p));

	// draw cartwheel octagon
	//var sp = scale_points([cartwheels[k], p[0]]);
	//draw_quick_octagon(cartwheels[k], get_distance(sp[0], sp[1]), cartwheels[k].color);
	//draw_cartwheel(cartwheels[k]);
	fresh_tiles.push(cartwheels[k]);
	fresh_cartwheels.push(cartwheels[k]);
	return cartwheels[k];
}

function draw_cartwheel_bar(o) {
	draw_bars([o], .1, 1);
}

function draw_cartwheel(o) {
	if (drawn[o.k]) { return; }
	drawn[o.k] = true;
	var sp = scale_points([o, o.verts[0]]);
	if (opts['octagon']) {
	//if (o.orientation != 7) { return; }
	draw_quick_octagon(o, get_distance(sp[0], sp[1]), o.color);
	}
	for (var g in o.lines) {
		draw_bars(o.lines[g], .1, 1);
	}
}

function find_diamond_a4_edges(t, d) {
	if (null != t.a4) { return; }
	//if (d) {return;}
	var l = [t.verts[0], t.verts[2]];
	var s = [t.verts[1], t.verts[3]];
	t.a4 = {};
       	t.a4[d ? 'd' : 'c'] = {
		l: l,
		s: s
	};
	if (
		(opts['a4'] && !d) ||
		(opts['a4d'] && d)
	) {
		ctx.strokeStyle = 'rgb(127,127,127)';
		ctx.lineWidth = 1;
		draw_segments(scale_points(l));
		draw_segments(scale_points(s));
	}
}

function find_square_a4_edges(t, a, c, r) {
	if (null != t.a4) { return; }
	//draw_quick_dot(a[0], null, 'red');
	//draw_quick_dot(a[1], null, 'green');
	//draw_quick_dot(a[2], null, 'blue');
	ctx.strokeStyle = 'rgb(127,127,127)';
	ctx.lineWidth = 1;
	//draw_segments(scale_points([a[0], a[2]]));

	var th0;
	var ix0;
	var ix1;
	if (0 == (c.orientation % 4)) {
		th0 = Math.PI / 2;
		ix0 = 0;
		ix1 = 3;
		ix2 = 2;
		ix3 = 1;
		//ctx.strokeStyle = 'yellow';
	} else if (2 == (c.orientation % 4)) {
		th0 = 0;
		ix0 = 0;
		ix1 = 3;
		ix2 = 2;
		ix3 = 1;
		//ctx.strokeStyle = 'blue';
	} else if (
		((!r) && (1 == (c.orientation % 4))) ||
		((r) && (3 == (c.orientation % 4)))
	) {
		th0 = Math.PI / 2; 
		ix0 = 0;
		ix1 = 1;
		ix2 = 2;
		ix3 = 3;
		//ctx.strokeStyle = 'green';
	} else {
		th0 = 0; 
		ix0 = 0;
		ix1 = 1;
		ix2 = 2;
		ix3 = 3;
		//ctx.strokeStyle = 'red';
	}

	if (1 == (c.orientation % 4)) {
		dh0 = Math.PI / 4;
		dx0 = 0;
		dx1 = 3;
		dx2 = 2;
		dx3 = 1;
		//ctx.strokeStyle = 'yellow';
	} else if (3 == (c.orientation % 4)) {
		dh0 = Math.PI / 4 + Math.PI / 2;
		dx0 = 0;
		dx1 = 3;
		dx2 = 2;
		dx3 = 1;
		//ctx.strokeStyle = 'blue';
	} else if (
		((!r) && (2 == (c.orientation % 4))) ||
		((r) && (0 == (c.orientation % 4)))
	) {
		dh0 = Math.PI / 4; 
		dx0 = 0;
		dx1 = 1;
		dx2 = 2;
		dx3 = 3;
		//ctx.strokeStyle = 'green';
	} else {
		dh0 = Math.PI / 4 + Math.PI / 2;
		dx0 = 0;
		dx1 = 1;
		dx2 = 2;
		dx3 = 3;
		//ctx.strokeStyle = 'red';
	}

	t.a4 = {};

	var b0 = get_bar(a[ix0].x, a[ix0].y, th0);
	var b1 = get_bar(a[ix1].x, a[ix1].y, th0 + Math.PI / 2);
	var b2 = get_bar(a[ix2].x, a[ix2].y, th0);
	var p0 = get_intersection(b0, b1);
	var p1 = get_intersection(b2, b1);

	var d0 = get_bar(a[dx0].x, a[dx0].y, dh0);
	var d1 = get_bar(a[dx1].x, a[dx1].y, dh0 + Math.PI / 2);
	var d2 = get_bar(a[dx2].x, a[dx2].y, dh0);
	var q0 = get_intersection(d0, d1);
	var q1 = get_intersection(d2, d1);

	if (opts['a4']) {
		draw_segments(scale_points([a[ix0], p0]));
		draw_segments(scale_points([a[ix1], p1]));
		draw_segments(scale_points([a[ix2], p1]));
	}
	if (opts['a4d']) {
		draw_segments(scale_points([a[dx0], q0]));
		draw_segments(scale_points([a[dx1], q1]));
		draw_segments(scale_points([a[dx2], q1]));
	}
	//draw_quick_dot(p0, null, 'orange');
	//draw_quick_dot(p1, null, 'orange');
}

function find_cartwheel_a4_edges(c) {
	//debug_log('find_a4 ' + c.k + " " + c.orientation);
	var d = (1 == (c.orientation % 2));

	find_diamond_a4_edges(c.tiles['top'], d);
	find_diamond_a4_edges(c.tiles['bottom'], d);
	find_diamond_a4_edges(c.tiles['left'], !d);
	find_diamond_a4_edges(c.tiles['right'], !d);
	find_square_a4_edges(c.tiles['left_square'], c.left_verts, c)
	find_square_a4_edges(c.tiles['right_square'], c.right_verts, c, true)
	return;
}

function find_a5_tiles(c) {
	var o = parseInt(c.orientation);
	if (null != c.tiles) { return; }
	c.tiles = [];
	//debug_log('orientation ' + o);
	var a = [];
	a['top'] = [
		c.verts[(o + 1) % 8],
		c.inner_verts[o],
		c.verts[(o + 7) % 8],
		c.verts[o], 
		'diamond'
	];
	c.left_verts = [
		c.verts[(o + 7) % 8],
		c.inner_verts[o],
		c.inner_verts[(o + 5) % 8],
		c.verts[(o + 6) % 8],
		'square'
	]
	a['left_square'] = c.left_verts;
	a['left'] = [
		c.verts[(o + 4) % 8],
		c.verts[(o + 5) % 8],
		c.verts[(o + 6) % 8],
		c.inner_verts[(o + 5) % 8],
		'diamond'
	];
	c.right_verts = [
		c.verts[(o + 1) % 8],
		c.inner_verts[o],
		c.inner_verts[(o + 3) % 8],
		c.verts[(o + 2) % 8],
		'square'
	];
	a['right_square'] = c.right_verts;
	a['right'] = [
		c.verts[(o + 4) % 8],
		c.verts[(o + 3) % 8],
		c.verts[(o + 2) % 8],
		c.inner_verts[(o + 3) % 8],
		'diamond'
	];
	a['bottom'] = [
		c.inner_verts[o],
		c.inner_verts[(o + 3) % 8],
		c.verts[(o + 4) % 8],
		c.inner_verts[(o + 5) % 8],
		'diamond'
	];
	for (var i in a) {
		c.tiles[i] = find_a5_tile(a[i]);
	}
}

function find_a5_tile(a) {
	var p = a.slice(0);
	var t = p.pop();
	var k = [];
	for (var i in p) {
		k.push(p[i].k);
	}
	k.sort();
	k = '<' + k.join('-') + '>';
	//debug_log(k);
	if (null != a5_tiles[k]) { return a5_tiles[k]; }

	var o = {k: k};
	o.typ = 'a5_tile';
	o.verts = p;
	o.edges = [];
	o.t = t;
	for (var i = 0; i < 4; ++i) {
		//debug_log(i);
		//debug_log('finding for ' + p[i].k);
		var e;
		e = find_a5_edge([p[i], p[(i + 1) % 4]]);
		o.edges[e.k] = a5_edges[e.k];
		e = find_a5_edge([p[i], p[(i + 3) % 4]]);
		o.edges[e.k] = a5_edges[e.k];
	}
	o.color = find_color(o.verts);
	a5_tiles[k] = o;
	//draw_a5_tile(o);
	fresh_tiles.push(o);
	return o;
}

function draw_a5_tile(o) {
	if (drawn[o.k]) { return; }
	drawn[o.k] = true;
	ctx.fillStyle = parse_color(o.color, (opts['edges'] ? .25 : .5));
	// fill a5 tile
	if (opts['fill']) { fill_polygon(scale_points(o.verts)); }
	if (opts['edges']) {
		for (var i in o.edges) { draw_a5_edge(o.edges[i]); }
	}
}

function draw_a5_edge(o) {
	if (drawn[o.k]) { return; }
	drawn[o.k] = true;
	ctx.strokeStyle = ((opts['fill'] || opts['gray'] || opts['bars']) ? 'rgb(127,127,127)' : parse_color(o.color, (opts['octagon'] ? .5 : 1)));
	ctx.lineWidth = 1;
	var a = [];
	for (var i in o.verts) { a.push(o.verts[i]); }
	//debug_log(a);
	// draw a5 tile edge
	draw_segments(scale_points(a));
	/*
	for (var i in o.verts) {
		draw_bars(o.verts[i].lines, .1, 1);
	}
	*/
}

function find_a5_edge(a) {
	var k = [];
	for (var i in a) {
		k.push(a[i].k)
	}
	k.sort();
	k = 'e[' + k.join('-') + ']';
	if (null != a5_edges[k]) { return a5_edges[k]; }
	//debug_log(k);
	var o = {k: k};
	o.verts = [];
	o.tiles = [];
	for (var i in a) {
		o.verts[a[i].k] = a[i];
		if (null == a[i].edges) {
			a[i].edges = [];
		}
		a[i].edges[k] = o;
	}
	o.color = find_color(a);
	a5_edges[k] = o;
	return o;
}

function find_opposite_a5_vert(vert, b) {
	var k = [];
	for (var i in b) { k.push(b[i].k); }
	k.sort();
	k = k.join ('|');
	if (null == vert.opposites) { vert.opposites = []; }
	if (null != vert.opposites[k]) { return vert.opposites[k]; }

	//debug_log(k);
	var a = vert.lines.slice(0);
	var bb = [];
	var bl = [];
	for (var i in a) { bl[a[i].k] = a[i]; }
	for (var i in b) { bb[b[i].k] = b[i]; }
	var n = a.length;
	while ( (null == bb[a[0].k]) || (null == bb[a[3].k]) ) {
		a.push(a.shift());
	}
	var ik = [];
	for (var i in a) { ik.push(a[i].k); }
	//debug_log(ik.join('|') + ' = ' + k);
	// the octagon we're looking for shares the two bars in bb with this
	var o = [];
	o[0] = a[0]
	o[1] = find_bar(a[1].g, a[1].i + 1);
	if (null != bl[o[1].k]) {
		o[1] = find_bar(a[1].g, a[1].i - 1);
	}
	//draw_quick_dot(get_intersection(o[0].bar, o[1].bar));
	o[2] = find_bar(a[2].g, a[2].i + 1);
	if (null != bl[o[2].k]) {
		o[2] = find_bar(a[2].g, a[2].i - 1);
	}
	//draw_quick_dot(get_intersection(o[1].bar, o[2].bar));
	o[3] = a[3];
	//draw_quick_dot(get_intersection(o[2].bar, o[3].bar));
	for (var i = 0; i < 4; ++i) {
		o[i + 4] = find_bar(o[i].g, o[i].i + 1);
		if (null != bl[o[i + 4].k]) {
			o[i + 4] = find_bar(o[i].g, o[i].i - 1);
		}
		//draw_quick_dot(get_intersection(o[i + 3].bar, o[i + 4].bar));
	}
	//draw_quick_dot(get_intersection(o[7].bar, o[0].bar));
	vert.opposites[k] = find_a5_vert(o);
	if (null == vert.opposites[k].opposites) {
		vert.opposites[k].opposites = [];
	}
	vert.opposites[k].opposites[k] = vert;
	return vert.opposites[k];
}

function orient_cartwheel(c) {
	//first, find the verts with two bars defining them
	var b = [];
	var ab = [];
	for (var i in c.bars) { b[c.bars[i].k] = c.bars[i]; }

	var a = [];
	for (var i in c.verts) {
		var bb = [];
		var n = 0;
		for (j in c.verts[i].lines) {
			if (null != b[c.verts[i].lines[j].k]) {
				//debug_log('push ' + c.verts[i].lines[j].k + ' for ' + c.verts[i].k);
				bb.push(c.verts[i].lines[j]);
				++n;
			}
		}
		if (2 == n) { 
			//draw_quick_dot(c.verts[i]);
			a.push(i);
			ab.push(bb.slice(0));
		}
	}

	// find the verts that are only 2 apart
	c.inner_verts = [];
	for (var i = 0; i < 3; ++ i) {
		var m = parseInt(a[i]);
		var n = parseInt(a[(i + 1) % 3]);
		if (n < m) { n += 8; }
		//debug_log(n + ' - ' + m + ' = ' + (n - m));
		if (2 == ( n - m )) {
			c.orientation = a[(i + 2) % 3];
		}
		c.inner_verts[a[i]] = find_opposite_a5_vert(c.verts[a[i]], ab[i])
	}
	
}

function find_a5_vert(a) {
	var k = [];
	for (var i in a) {
		k.push(a[i].k);
	}
	k.sort();
	k = '<' + k.join('|') + '>';
	if (null != a5_verts[k]) { return a5_verts[k]; }
	var o = {k: k};
	o.color = find_color(a);
	a5_verts[k] = o;
	var p = [];
	p.push(find_intersection(a[0], a[1]));
	p.push(find_intersection(a[2], a[1]));
	p.push(find_intersection(a[2], a[3]));
	p.push(find_intersection(a[4], a[3]));
	p.push(find_intersection(a[4], a[5]));
	p.push(find_intersection(a[6], a[5]));
	p.push(find_intersection(a[6], a[7]));
	p.push(find_intersection(a[0], a[7]));

	/*
	for (var i in p) {
		draw_quick_dot(p[i], 2, parse_color(o.color));
	}
	*/
	var pt = get_polygon_center(p);
	o.x = pt.x;
	o.y = pt.y;
	o.lines = a.slice(0);
	//draw_quick_dot(o, 2, parse_color(o.color));
	//debug_log(k);
	return a5_verts[k];
}

function find_cartwheel_verts(c) {

	var b = c.lines;
	var a = [

	find_a5_vert([
		b[0][8], 
		b[3][8], 
		b[2][2], 
		b[1][3], 
		b[0][7], 
		b[3][7], 
		b[2][3], 
		b[1][2]
	]),

	find_a5_vert([
		b[3][8], 
		b[2][0], 
		b[1][6], 
		b[0][5], 
		b[3][7], 
		b[2][1], 
		b[1][5], 
		b[0][6]
	]),

	find_a5_vert([
		b[2][0], 
		b[1][8], 
		b[0][2], 
		b[3][5], 
		b[2][1], 
		b[1][7], 
		b[0][3],
		b[3][6]
	]),

	find_a5_vert([
		b[1][8], 
		b[0][0], 
		b[3][2], 
		b[2][3], 
		b[1][7], 
		b[0][1],
		b[3][3],
		b[2][2] 
	]),

	find_a5_vert([
		b[0][0], 
		b[3][0], 
		b[2][6], 
		b[1][5], 
		b[0][1],
		b[3][1],
		b[2][5],
		b[1][6] 
	]),

	find_a5_vert([
		b[3][0], 
		b[2][8], 
		b[1][2], 
		b[0][3],
		b[3][1],
		b[2][7],
		b[1][3], 
		b[0][2]
	]),

	find_a5_vert([
		b[2][8], 
		b[1][0], 
		b[0][6],
		b[3][3],
		b[2][7],
		b[1][1], 
		b[0][5],
		b[3][2] 
	]),

	find_a5_vert([
		b[1][0], 
		b[0][8],
		b[3][6],
		b[2][5],
		b[1][1], 
		b[0][7],
		b[3][5], 
		b[2][6]
	])
	];

	return a;
}

function draw_quick_dot(d, r, c) {
	var dot = new_draw_dot();
	dot.clr = ((null == c) ? "white" : c);
	dot.r = ((null == r) ? 2 : r);

	var p = scale_points([d])[0];
	dot.x = p.x;
	dot.y = p.y;
	draw_dot(dot);
}

function draw_segments(b) {
	//log_bar("draw", b);
	ctx.save();
	ctx.beginPath();
	ctx.moveTo(b[0].x, b[0].y);
	for (var i = 1; i < b.length; ++i) {
		ctx.lineTo(b[i].x, b[i].y);
		//log_bar("draw", b);
	}
	ctx.stroke();
	ctx.restore();
}

function draw_bars(b, n, bold) {
	for (var k in b) {
		if (null != drawn[b[k].k]) { continue; } 
		drawn[b[k].k] = true;
		var bar = b[k];
		//var clr = rainbow_color(.5 + bar.d / 24);
		var clr = parse_color(bar.color);

		if (opts['gray']) { clr = 'white'; }
		ctx.strokeStyle = clr;
		ctx.lineWidth = 1;
		if (null != n) {ctx.lineWidth = n};
		if (null != bold) {
			if (b[k].bold) {
				ctx.lineWidth = bold;
			}
		}
		draw_bar(bar.bar);
		ctx.lineWidth = 1.0;
	}
}

function draw_bar(s) {
	if (! opts['bars']) { return; }
	var b = find_view_bar(s);
	if (null == b) {return; }
	b = scale_points(b);
	draw_segments(b);
}

function find_view_bar(s) {
	var o = new Array();
	o.push(get_intersection(s, v.l));
	o.push(get_intersection(s, v.t));
	o.push(get_intersection(s, v.r));
	o.push(get_intersection(s, v.b));

	// remove all points that are outside the viewport

	var b = new Array();
	var p;
	while (o.length > 0) {
		p = o.shift();
		if (null == p) { continue; }
		if (p.x < v.l[0].x - .00001) { continue; }
		if (p.x > v.r[0].x + .00001) { continue; }
		if (p.y < v.t[0].y - .00001) { continue; }
		if (p.y > v.b[0].y + .00001) { continue; }
		b.push(p);
		if (2 == b.length) {break; }
	}
	//log_bar("scaled", b);
	if (2 == b.length) {return b;}
	return null;
}

function find_intersection(a, b) {
	var k = [a.k, b.k];
	k.sort();
	k = '[' + k.join('][') + ']';
	//debug_log(k);
	var o = ints[k];
	if (null == o) {
		o = get_intersection(a.bar, b.bar);
		o.bars = [];
		o.bars[a.k] = a;
		o.bars[b.k] = b;
		o.k = k;
		ints[k] = o;
	}
	return o;
}

function get_intersection(a, b) {
	//log_bar("a", a);
	//log_bar("b", b);
	var p1 = a[0];
	var p2 = a[1];
	var p3 = b[0];
	var p4 = b[1];

	// p1 and p2 are on one line,
	// p3 and p4 are on the other
	
	var d = (
		(p4.y - p3.y) * (p2.x - p1.x) -
		(p4.x - p3.x) * (p2.y - p1.y)
	);
	if (0 == d) { return null; }

	var u = (
		(p4.x - p3.x) * (p1.y - p3.y) -
		(p4.y - p3.y) * (p1.x - p3.x)
	) / d;

	var o = new Object();
	o.x = p1.x + u * (p2.x - p1.x);
	o.y = p1.y + u * (p2.y - p1.y);

	o.k = ('(' + o.x + ',' + o.y + ')');
	//log_bar("intersects", [o]);
	return o;
}

function scale_points(p) {
	var o = new Array();
	for (var i in p) {
		o[i] = {
			x: p[i].x * v.z + v.cx - v.x * v.z, 
			y: p[i].y * v.z + v.cy - v.y * v.z
		}
	}
	return o;
}

function draw_segments(b) {
	//log_bar("draw", b);
	ctx.save();
	ctx.beginPath();
	ctx.moveTo(b[0].x, b[0].y);
	for (var i = 1; i < b.length; ++i) {
		ctx.lineTo(b[i].x, b[i].y);
		//log_bar("draw", b);
	}
	ctx.stroke();
	ctx.restore();
}

function get_midpoint(p1, p2) {
	var o = new Object();
	o.x = (p1.x + p2.x) / 2;
	o.y = (p1.y + p2.y) / 2;
	return o;
}

function get_polygon_center(p) {
	var a = [];
	for (var i in p) { a.push(p[i]); }
	var o = {x: 0, y:0};
	for (var i in a) {
		o.x += a[i].x / a.length;
		o.y += a[i].y / a.length;
	}
	return o;
}

function get_midpoint_r(r, p1, p2) {
	var dx = (p2.x - p1.x);
	var dy = (p2.y - p1.y);
	var d = Math.sqrt(dx * dx + dy * dy);
	var th = Math.atan2(dy, dx);
	var o = new Object();
	o.x = p2.x + r * d * Math.cos(th);
	o.y = p2.y + r * d * Math.sin(th);
	return o;
}

function get_midpoint_d(d, p1, p2) {
	var dx = (p2.x - p1.x);
	var dy = (p2.y - p1.y);
	var th = Math.atan2(dy, dx);
	var o = new Object();
	o.x = p1.x + d * Math.cos(th);
	o.y = p1.y + d * Math.sin(th);
	return o;
}

function fill_polygon(p) {
	ctx.save();
	ctx.beginPath();
	ctx.moveTo(p[0].x, p[0].y);
	for (i = 1; i < p.length; ++i) {
		ctx.lineTo(p[i].x, p[i].y);
	}
	ctx.closePath();
	ctx.fill();
	ctx.restore();
	return p;
}

function draw_polygon(p) {
	ctx.save();
	ctx.beginPath();
	ctx.moveTo(p[0].x, p[0].y);
	for (i = 1; i < p.length; ++i) {
		ctx.lineTo(p[i].x, p[i].y);
	}
	ctx.closePath();
	ctx.stroke();
	ctx.restore();
	return p;
}

function find_color(b) {
	var o = [0, 0, 0];
	var n = 0;
	for (var i in b) {
		if (null == b[i].color) {continue;}
		++n;
		for (var j in b[i].color) {
			o[j] += b[i].color[j];
		}
	}

	for (var i in o) {
		o[i] = Math.floor(o[i] / n);
	}

	return o;
}

function parse_color(c, d) {
	var n = ((null == d) ? 1 : d);
	var o = [];
	for (var i in c) {
		o.push(Math.floor(n * c[i]));
	}
	return 'rgb(' + o.join(',') + ')';
}

function draw_quick_octagon(d, r, clr, a) {
	var c = parse_color(clr, .25);
	//draw_quick_dot(d, r, c);
	//if (opts['cartwheel']) { draw_quick_dot(d, r, c); }
	//if (! opts['decagon']) { return; }
	if (null != a) {
		c = 'rgb(' + a.join(',') + ')';
	}
	var p = scale_points([d])[0];

	ctx.strokeStyle = ((null == c) ? "white" : c);
	//var nw = (opts['edges'] ? 3 : opts['colors'] ? 2 : 1);
	nw = 4;
	var nr = r - nw;
	if (nr < 0) {nr = 0;}
	ctx.lineWidth = nw;

	p = get_regular_polygon_points(8, p.x, p.y, nr, Math.PI / 2);
	draw_polygon(p);
	/*
	if (null != c) {
		ctx.fillStyle = c;
		fill_polygon(p);
	}
	*/
}

function get_regular_polygon_points(sides, x, y, r, theta) {
	var p = new Array();
	var th;
	var i;
	for (i = 0; i < sides; ++i) {
		th = theta + i * 2 * Math.PI / sides;
		p[i] = new Object();
		p[i].x = x + r * Math.cos(th);
		p[i].y = y - r * Math.sin(th);
	}
	return p;
}

function get_distance(p1, p2) {
	/*
	debug_log('get_distance 1');
	debug_log(p1);
	debug_log('get_distance 2');
	debug_log(p2);
	*/
	var dx = (p2.x - p1.x);
	var dy = (p2.y - p1.y);
	return Math.sqrt(dx * dx + dy * dy);
}

function find_seed_cartwheel(t) {
	if (null != expand) { clearTimeout(expand); }
	if (null == seed) { 
		if (null == seed_a) { 
			seed_a = [0,0,0,0]; 
		}
		seed = find_cartwheel(seed_a); 
		seed_d = get_distance(seed, v);
	}
	if (cartwheel_inside(seed)) { return task_expand(10); }
	fresh_cartwheels = [];
	var d = get_distance(seed, v);
	seed.tangents = find_tangent_cartwheels(seed.a);
	while (fresh_cartwheels.length > 0) {
		var c = fresh_cartwheels.shift();
		var cd = get_distance(c, v);
		if (cd < d) {
			seed = c;
			seed_a = c.a;
			d = cd;
			seed_d = cd;
		}
	}
	fresh_cartwheels = [seed];
	expand = setTimeout('find_seed_cartwheel(' + t + ')', t);
}

function task_expand(t) {
	if (null != expand) { clearTimeout(expand); }
	expand = setTimeout('expand_cartwheels(' + t + ')', t);
}

function cartwheel_inside(c) {
return (null != c) && (
	(c.x + cartwheel_r > v.l[0].x) &&
	(c.x - cartwheel_r < v.r[0].x) &&
	(c.y + cartwheel_r > v.t[0].y) &&
	(c.y - cartwheel_r < v.b[0].y)
	) 
}

function cartwheel_sort(a, b) {
	//debug_log('cartwheel_sort');
	var ad = get_distance(a, v);
	var bd = get_distance(b, v);
	if (ad < bd) { return -1; }
	if (ad > bd) { return 1; }
	return 0;
}

function draw_thing(o) {
	if ('a5_tile' == o.typ) { draw_a5_tile(o); }
	if ('cartwheel' == o.typ) { draw_cartwheel(o); }
	if ('bar' == o.typ) { draw_cartwheel_bar(o); }
}

function expand_cartwheels(t) {
	while (fresh_tiles.length > 0) {
		var nt = fresh_tiles.shift();
		draw_tiles.push(nt);
	}

	var dn = Math.floor(Math.random() * 32);
	while (draw_tiles.length > 0 && dn > 0) {
		--dn;
		var tmp = draw_tiles[0];
		var tn = Math.floor(Math.random() * draw_tiles.length);
		draw_tiles[0] = draw_tiles[tn];
		draw_tiles[tn] = tmp;
		draw_thing(draw_tiles.shift());
	}

	if (0 == fresh_cartwheels.length && 0 == draw_tiles.length) { 
		//status_update("ready");
		/*
		while (draw_tiles.length > 0) {
			draw_thing(draw_tiles.shift());
		}
		*/
		done_drawing();
		return; 
	}

	var n = 4;
	while (n > 0 && fresh_cartwheels.length > 0) {
		--n;

		fresh_cartwheels.sort(cartwheel_sort);
		var rn = 1;
		for (var i = 0; i < 3; ++i) { rn *= Math.random(); }

		var cn = Math.floor(rn * fresh_cartwheels.length);
		var c = fresh_cartwheels[cn];
		fresh_cartwheels[cn] = fresh_cartwheels[0];
		fresh_cartwheels.shift();
		var d = get_distance(c, v);
		if (d < seed_d) { seed_d = d; seed_a = c.a; }
		if (cartwheel_inside(c)) { 
			c.tangents = find_tangent_cartwheels(c.a); 
			//find_cartwheel_p3_tiles(c);
			/*
			for (var i in c.tangents) {
				if (null == c.tangents[i]) { continue; }
				find_tangent_cartwheels(c.tangents[i]);
				find_cartwheel_p3_tiles(c.tangents[i]);
			}
			*/
		}
	}
	task_expand(t);
}

function done_drawing() {
	//alert('done');
	debug_log('done');
	is_drawing = false;
	if (auto_update) {
		auto_timeout = setTimeout('random_redraw()', 5000);
	}
}
